package com.lixueju.security.box.web.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lixueju.security.box.core.dto.SecurityBoxUtils;
import com.lixueju.security.box.core.enums.TableFieldEnum;
import com.lixueju.security.box.core.exception.SecurityBoxException;
import com.lixueju.security.box.core.model.AuthUserRoleRel;
import com.lixueju.security.box.core.model.User;
import com.lixueju.security.box.core.model.dto.UserToken;
import com.lixueju.security.box.core.model.request.*;
import com.lixueju.security.box.core.properties.SecurityBoxConstants;
import com.lixueju.security.box.web.mapper.UserMapper;
import com.lixueju.security.box.web.service.IAuthUserRoleRelService;
import com.lixueju.security.box.web.service.IUserService;
import com.lixueju.security.box.web.support.SecurityBoxBindWeixinAfter;
import com.lixueju.security.box.web.support.SecurityBoxBindMobileAfter;
import com.lixueju.security.box.web.weixin.WeixinAuthService;
import com.lixueju.security.box.web.weixin.model.CodeResponse;
import com.lixueju.security.box.web.weixin.model.WeixinPhoneNumber;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户基础表，存储用户基本信息 服务实现类
 * </p>
 *
 * @author lixueju
 * @since 2019-11-13
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private IAuthUserRoleRelService authUserRoleRelService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private WeixinAuthService weixinAuthService;

    @Autowired
    protected SecurityBoxUtils securityBoxUtils;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private SecurityBoxBindMobileAfter securityBoxBindWeixinMobileAfter;

    @Autowired
    private SecurityBoxBindWeixinAfter securityBoxBindWeixinAfter;

    @Override
    public IPage<User> selectUserPage(Page<User> page, User user) {
        QueryWrapper<User> quUser = new QueryWrapper<>();
        quUser.eq("delFlag", TableFieldEnum.AllOfdelFlag.no.code());
        if (!StringUtils.isBlank(user.getUsername())) {
            quUser.like("username", user.getUsername());
        }
        this.page(page, quUser);
        List<User> records = page.getRecords();
        if (CollectionUtils.isEmpty(records)) {
            return page;
        }
        Object[] userIds = new Object[records.size()];
        for (int i = 0; i < records.size(); i++) {
            userIds[i] = records.get(i).getId();
        }
        QueryWrapper<AuthUserRoleRel> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("userId", userIds);
        Map<Long, List<AuthUserRoleRel>> userRoleMap = authUserRoleRelService.list(queryWrapper).stream().collect(Collectors.groupingBy(AuthUserRoleRel::getUserId));
        for (User record : records) {
            record.setPassword(null);
            List<AuthUserRoleRel> authUserRoleRels = userRoleMap.get(record.getId());
            if (CollectionUtils.isEmpty(authUserRoleRels)) {
                record.getExtend().put("roleIds", new ArrayList<>());
                continue;
            }
            record.getExtend().put("roleIds", authUserRoleRels.stream().map(o -> o.getRoleId() + "").collect(Collectors.toSet()));
        }
        return page;
    }

    @Override
    public Boolean updateUserRole(UpdateUserRequest updateUserRequest) {
        if (updateUserRequest.getId() == null) {
            updateUserRequest.setId(IdWorker.getId());
            addUser(updateUserRequest);
        } else {
            updateUser(updateUserRequest);
        }
        QueryWrapper<AuthUserRoleRel> delAuthUserRoleRel = new QueryWrapper<>();
        delAuthUserRoleRel.eq("userId", updateUserRequest.getId());
        authUserRoleRelService.remove(delAuthUserRoleRel);

        List<AuthUserRoleRel> newList = new ArrayList<>();
        for (Long roleId : updateUserRequest.getRoleIds()) {
            AuthUserRoleRel authUserRoleRel = new AuthUserRoleRel();
            authUserRoleRel.setUserId(updateUserRequest.getId());
            authUserRoleRel.setRoleId(roleId);
            newList.add(authUserRoleRel);
        }
        if (newList.size() > 0) {
            authUserRoleRelService.saveBatch(newList);
        }
        return true;
    }

    @Override
    public Boolean addUser(User user) {
        if (StringUtils.isNotBlank(user.getPassword())) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
        }
        if (this.checkUserName(user.getUsername(), "", user.getUserType())) {
            throw new SecurityBoxException("用户名已存在");
        }
        if (this.checkMobile(user.getMobile(), "", user.getUserType())) {
            throw new SecurityBoxException("手机号已存在");
        }
        return this.save(user);
    }

    @Override
    public Boolean updateUser(User user) {
        if (user.getId() == null) {
            throw new SecurityBoxException("用户id不能为空");
        }
        User userOne = this.getById(user.getId());
        if (userOne == null) {
            throw new SecurityBoxException("该用户不存在");
        }
        if (this.checkUserName(user.getUsername(), userOne.getUsername(), user.getUserType())) {
            throw new SecurityBoxException("用户名已存在");
        }
        if (this.checkMobile(user.getMobile(), userOne.getMobile(), user.getUserType())) {
            throw new SecurityBoxException("手机号已存在");
        }
        BeanUtils.copyProperties(user, userOne);
        userOne.setPassword(null);
        userOne.setUpdateTime(null);
        return this.updateById(userOne);
    }

    private Boolean checkUserName(String username, String oldUsername, Integer userType) {
        if (StringUtils.isBlank(username)) {
            return false;
        }
        if (StringUtils.isNotBlank(oldUsername) && username.equals(oldUsername)) {//新旧手机号相同[修改操作]，不做检查
            //不在检查oldMobile是不是用户更新前的手机号了
            return false;
        }
        QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
        queryWrapper.eq("delFlag", TableFieldEnum.AllOfdelFlag.no.code());
        queryWrapper.eq("userType", userType);
        queryWrapper.eq("username", username);
        Integer num = this.count(queryWrapper);
        if (num == 0) {
            return false;
        }
        return true;//调用自定义错误枚举码
    }

    @Override
    public Boolean checkMobile(String mobile, String oldMobile, Integer userType) {
        if (StringUtils.isBlank(mobile)) {
            return false;
        }
        if (StringUtils.isNotBlank(oldMobile) && mobile.equals(oldMobile)) {//新旧手机号相同[修改操作]，不做检查
            //不在检查oldMobile是不是用户更新前的手机号了
            return false;
        }
        User userByMobile = this.getUserByMobile(mobile, userType);
        if (userByMobile == null) {
            return false;
        }
        return true;//调用自定义错误枚举码
    }

    private User getUserByMobile(String mobile, Integer userType) {
        QueryWrapper<User> quUserByMobile = new QueryWrapper<>();
        quUserByMobile.eq("mobile", mobile);
        quUserByMobile.eq("userType", userType);
        quUserByMobile.eq("delFlag", TableFieldEnum.AllOfdelFlag.no.code());
        return this.getOne(quUserByMobile);
    }

    @Override
    public UserToken bindMobile(BindMobileRequest bindMobileRequest, Long userId, HttpServletRequest request) {
        return this.bindMobile(bindMobileRequest.getMobile(), userId, request);
    }

    @Override
    public Boolean upPassword(upPasswordRequest upPasswordRequest, Long userId) {
        if (StringUtils.isBlank(upPasswordRequest.getOldPassword())) {
            throw new SecurityBoxException("旧密码不可为空");
        }
        if (StringUtils.isBlank(upPasswordRequest.getNewPassword())) {
            throw new SecurityBoxException("新密码不可为空");
        }
        if (StringUtils.isBlank(upPasswordRequest.getReNewPassword())) {
            throw new SecurityBoxException("确认新密码不可为空");
        }
        User user = this.getById(userId);
        if (user == null) {
            throw new SecurityBoxException("用户不存在");
        }
        if (!passwordEncoder.matches(upPasswordRequest.getOldPassword(), user.getPassword())) {
            throw new SecurityBoxException("旧密码不正确");
        }
        if (!upPasswordRequest.getNewPassword().equals(upPasswordRequest.getReNewPassword())) {
            throw new SecurityBoxException("两次新密码不一样");
        }
        user.setPassword(passwordEncoder.encode(upPasswordRequest.getNewPassword()));
        user.setUpdateTime(null);
        this.updateById(user);
        return true;
    }

    @Override
    public Boolean upPasswordByMobile(upPasswordRequest upPasswordRequest) {
        if (StringUtils.isBlank(upPasswordRequest.getNewPassword())) {
            throw new SecurityBoxException("新密码不可为空");
        }
        if (StringUtils.isBlank(upPasswordRequest.getReNewPassword())) {
            throw new SecurityBoxException("确认新密码不可为空");
        }
        if (StringUtils.isBlank(upPasswordRequest.getMobile())) {
            throw new SecurityBoxException("手机号不可为空");
        }
        if (upPasswordRequest.getUserType() == null) {
            throw new SecurityBoxException("用户类型不可为空");
        }
        User user = this.getUserByMobile(upPasswordRequest.getMobile(), upPasswordRequest.getUserType());
        if (user == null) {
            throw new SecurityBoxException("用户不存在");
        }
        if (!upPasswordRequest.getNewPassword().equals(upPasswordRequest.getReNewPassword())) {
            throw new SecurityBoxException("两次新密码不一样");
        }
        user.setPassword(passwordEncoder.encode(upPasswordRequest.getNewPassword()));
        user.setUpdateTime(null);
        this.updateById(user);
        return true;
    }

    @Override
    public UserToken bindWeixinMobile(WeixinDecodeLoginRequest weixinDecodeLoginRequest, Long userId, HttpServletRequest request) {
        WeixinPhoneNumber weixinPhoneNumber = weixinAuthService.getWeixinPhoneNumber(weixinDecodeLoginRequest);
        if (weixinPhoneNumber == null || weixinPhoneNumber.getPurePhoneNumber() == null) {
            throw new SecurityBoxException("微信密文解析失败");
        }
        return this.bindMobile(weixinPhoneNumber.getPurePhoneNumber(), userId, request);
    }

    // 绑定手机号逻辑
    private UserToken bindMobile(String mobile, Long userId, HttpServletRequest request) {
        if (StringUtils.isBlank(mobile)) {
            throw new SecurityBoxException("手机号不可为空");
        }
        User loginUser = this.getById(userId);
        if (loginUser == null) {
            throw new SecurityBoxException("用户不存在");
        }
        QueryWrapper<User> quUserByMobile = new QueryWrapper<>();
        quUserByMobile.eq("mobile", mobile);
        quUserByMobile.eq("userType", loginUser.getUserType());
        quUserByMobile.eq("delFlag", TableFieldEnum.AllOfdelFlag.no.code());
        User userByMobile = this.getOne(quUserByMobile);
        String key = request.getHeader(SecurityBoxConstants.DEFAULT_TOKEN_KEY);
        UserToken userToken = null;
        if (userByMobile == null) {
            loginUser.setMobile(mobile);
            userToken = securityBoxUtils.getUserToken(key);
            userToken.setMobile(mobile);
        } else {
            if (StringUtils.isNotBlank(userByMobile.getWxBindingId())) {
                throw new SecurityBoxException("该手机号已经绑定其它微信");
            }
            userByMobile.setWxBindingId(loginUser.getWxBindingId());
            userByMobile.setToken(key);
            if (StringUtils.isBlank(userByMobile.getNickName())) {
                userByMobile.setNickName(loginUser.getNickName());
            }
            if (StringUtils.isBlank(userByMobile.getAvatarUrl())) {
                userByMobile.setAvatarUrl(loginUser.getAvatarUrl());
            }
            loginUser.setDelFlag(TableFieldEnum.AllOfdelFlag.yes.code());
            userToken = new UserToken();
            BeanUtils.copyProperties(userByMobile, userToken);

            this.updateById(userByMobile);
            securityBoxBindWeixinMobileAfter.bindMobileAfter(loginUser, userByMobile);
        }
        JSONObject jwtToken = securityBoxUtils.getJwtToken(key);
        jwtToken.put("userToken", userToken);
        Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        if (expire != null && expire > 0) {
            stringRedisTemplate.boundValueOps(key).set(JSONObject.toJSONString(jwtToken), expire, TimeUnit.SECONDS);
        }
        this.updateById(loginUser);
        return userToken;
    }

    @Override
    public UserToken bindWeixin(WeixinLoginRequest weixinLoginRequest, Long userId, HttpServletRequest request) {
        CodeResponse codeResponse = weixinAuthService.login(weixinLoginRequest);
        User user = this.getById(userId);
        user.setWxBindingId(codeResponse.getUnionid());
        this.updateById(user);
        securityBoxBindWeixinAfter.bindWeixinAfter(user);
        String key = request.getHeader(SecurityBoxConstants.DEFAULT_TOKEN_KEY);
        JSONObject jwtToken = securityBoxUtils.getJwtToken(key);
        UserToken userToken = jwtToken.getObject("userToken", UserToken.class);
        userToken.setWxBindingId(codeResponse.getUnionid());
        jwtToken.put("userToken", userToken);
        Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        if (expire != null && expire > 0) {
            stringRedisTemplate.boundValueOps(key).set(JSONObject.toJSONString(jwtToken), expire, TimeUnit.SECONDS);
        }
        return userToken;
    }

}
